﻿// <copyright file="VolumeFile.cs" company="Sinclair Community College">
// Copyright (c) Sinclair Community College. All rights reserved.
// </copyright>

namespace SinclairCC.AppV
{
    using System;
    using System.IO;

    // TODO: What about "Package Deleted Container" : The corresponding file or directory has been marked as deleted.
    // TODO: What about "Link Data Container" : The container points to a symbolic link.

    /// <summary>
    /// This class represents a volume file in Microsoft Application Virtualization (App-V).
    /// </summary>
    public class VolumeFile : IDisposable
    {
        /// <summary>
        /// A stream for reading the volume file.
        /// </summary>
        private FileStream inputStream;

        /// <summary>
        /// Reads binary data from the volume file stream.
        /// </summary>
        private BinaryReader fileReader;

        /// <summary>
        /// Stores the primary volume container.
        /// </summary>
        private VolumeContainer volContPrimary;

        /// <summary>
        /// Stores the secondary volume container.
        /// </summary>
        private VolumeContainer volContSecondary;

        /// <summary>
        /// Stores the Container Allocation Map container.
        /// </summary>
        private ContainerAllocationMap containerMap;

        /// <summary>
        /// Stores the security descriptor container.
        /// </summary>
        private SecurityDescriptorContainer securityDescriptors;

        /// <summary>
        /// Stores the storage allocation map container.
        /// </summary>
        private StorageAllocationMap storageAllocationMap;

        /// <summary>
        /// Stores the root directory container.
        /// </summary>
        private RootDirectoryContainer rootDirectory;

        private System.Collections.ObjectModel.Collection<Container> containerTable;

        /// <summary>
        /// Initializes a new instance of the VolumeFile class.
        /// </summary>
        /// <param name="filePath">The full path of a volume file.</param>
        public VolumeFile(string filePath)
        {
            this.containerTable = new System.Collections.ObjectModel.Collection<Container>();

            if (File.Exists(filePath))
            {
                int bytesRead = 0;

                this.inputStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                this.fileReader = new BinaryReader(this.inputStream, System.Text.Encoding.UTF8);

                uint containerSize = 0;
                uint storageBlockSize = 0;
                while ((this.fileReader.BaseStream.Position != this.fileReader.BaseStream.Length) && (containerSize <= 0))
                {
                    VolumeContainer vol = new VolumeContainer(this.fileReader);
                    containerSize = vol.DataInformationRegion.ContainerBlockSize;
                    storageBlockSize = vol.DataInformationRegion.StorageBlockSize;
                }

                if (containerSize <= 0) return;
                if (storageBlockSize <= 0) return;

                // Move back to the beginning of the file.
                this.fileReader.BaseStream.Seek(0, SeekOrigin.Begin);

                int containerCount = 0;

                byte[] buffer = new byte[containerSize];
                while ((this.fileReader.BaseStream.Position != this.fileReader.BaseStream.Length) && (containerCount < 16))
                {
                    bytesRead = this.fileReader.Read(buffer, 0, buffer.Length);

                    MemoryStream byteStream = new MemoryStream(buffer, false);
                    BinaryReader streamReader = new BinaryReader(byteStream);

                    // TODO: Is there a way to do this without creating a CommonHeader object, or creating it and passing it to the derived container constructor?
                    CommonHeader cnt = new CommonHeader(streamReader);
                    streamReader.BaseStream.Seek(0, SeekOrigin.Begin);

                    switch (cnt.TypeId)
                    {
                        case ContainerType.VOLUME_CONTAINER_P:
                            this.volContPrimary = new VolumeContainer(streamReader);
                            containerCount++;
                            break;
                        case ContainerType.VOLUME_CONTAINER_S:
                            this.volContSecondary = new VolumeContainer(streamReader);
                            containerCount++;
                            break;
                        case ContainerType.CONTAINER_ALLOCATION_MAP:
                            this.containerMap = new ContainerAllocationMap(streamReader);
                            containerCount++;
                            break;
                        case ContainerType.SECURITY_DESCRIPTOR:
                            this.securityDescriptors = new SecurityDescriptorContainer(streamReader);
                            containerCount++;
                            break;
                        case ContainerType.STORE_ALLOCATION_MAP:
                            this.storageAllocationMap = new StorageAllocationMap(streamReader);
                            containerCount++;
                            break;
                        case ContainerType.CONTAINER_RESERVED:
                            // Ignore reserved containers.
                            containerCount++;
                            break;
                        case ContainerType.PACKAGE_DIRECTORY:
                            this.rootDirectory = new RootDirectoryContainer(streamReader);
                            /*
                            Console.WriteLine("root directory container index: {0:N0}", this.rootDirectory.Header.ContainerIndex);
                            */
                            /*
                            if (rootDirectory.StorageBlockTable != null)
                            {
                                rootDirectory.Process(containerSize, storageBlockSize, fileReader);
                            }
                            */
                            containerCount++;
                            break;
                    }
                    streamReader.Close();
                    byteStream.Close();
                    byteStream.Dispose();
                }

                // TODO: Process the container allocation map and expand the container table.
                this.containerMap.Process(containerTable, containerSize, storageBlockSize, this.fileReader);

                // TODO: Process the remaining containers following the CSR bitmap and create a complete Container Table.
            }
            else
            {
                throw new FileNotFoundException("The specified file could not be found.", filePath);
            }
        }

        /*
        public RootDirectoryContainer RootDirectory
        {
            get { return this.rootDirectory; }
        }
        */

        public _DIRECTORY_STRUCT_PRIMARY[] GetRootDirectories()
        {
            _DIRECTORY_STRUCT_PRIMARY[] rootDirectories = new _DIRECTORY_STRUCT_PRIMARY[this.rootDirectory.ChildDirectories.Count];
            this.rootDirectory.ChildDirectories.CopyTo(rootDirectories, 0);
            return rootDirectories;
        }        
        
        public Container[] GetChildContainers(uint parentContainerIndex)
        {
            System.Collections.Generic.List<Container> returnList = new System.Collections.Generic.List<Container>();
            foreach (Container c in containerTable)
            {
                if (c.Header.ContainerIndex == parentContainerIndex)
                {
                    returnList.Add(c);
                }
            }
            Container[] returnArray = new Container[returnList.Count];
            returnList.CopyTo(returnArray);
            return returnArray;
        }
        

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.fileReader.Close();
            this.inputStream.Close();
            this.inputStream.Dispose();
        }

        #endregion
    }
}
